﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;

namespace HeresyManager
{
    [Serializable]
    public class Dice : IDice
    {
        internal static String D { get { return "D"; } }

        private static Random r = new Random();
        private readonly int numberOfPlains;

        public Dice(int numberOfPlains)
        {
            this.numberOfPlains = numberOfPlains;
        }


        public int Roll()
        {
            return r.Next(1, numberOfPlains + 1);
        }

        public static IDice D5 { get { return new Dice(5); } }
        public static IDice D6 { get { return new Dice(6); } }
        public static IDice D10 { get { return new Dice(10); } }
        public static IDice D100 { get { return new Dice(100); } }



        public override string ToString()
        {
            return D + numberOfPlains;
        }


        public int MinValue
        {
            get { return 1; }
        }

        public int MaxValue
        {
            get { return numberOfPlains; }
        }

        public double DurchnitsValue
        {
            get { return numberOfPlains * (numberOfPlains + 1) / 4; }
        }
    }

    [Serializable]
    public class DiceCollection : IDice
    {
        private IDice[] dices;
        public DiceCollection(params IDice[] dices)
        {
            this.dices = dices;
        }


        public int Roll()
        {
            return dices.Aggregate(0, (n, d) => n + d.Roll());
        }

        public override string ToString()
        {
            return dices.Aggregate("", (s, d) => s + "+ " + d.ToString(), s => s.Substring(2));
        }


        public int MinValue
        {
            get { return dices.Aggregate(0, (i, d) => d.MinValue + i); }
        }

        public int MaxValue
        {
            get { return dices.Aggregate(0, (i, d) => d.MaxValue + i); }
        }

        public double DurchnitsValue
        {
            get { return dices.Aggregate(0.0, (i, d) => d.DurchnitsValue + i); }
        }
    }

    [Serializable]
    public class DiceMultiplier : IDice
    {
        private IDice dice;
        private int multiplier;

        public DiceMultiplier(IDice dice, int multiplier)
        {
            this.dice = dice;
            this.multiplier = multiplier;
        }

        public int Roll()
        {
            int erg = 0;
            for (int i = 0; i < multiplier; i++)
                erg += dice.Roll();
            return erg;
        }
        public override string ToString()
        {
            if (dice is Dice)
                return multiplier + dice.ToString();
            else
                return multiplier + " * (" + dice.ToString() + ")";
        }


        public int MinValue
        {
            get { return dice.MinValue * multiplier; }
        }

        public int MaxValue
        {
            get { return dice.MaxValue * multiplier; }
        }

        public double DurchnitsValue
        {
            get { return dice.DurchnitsValue * multiplier; }
        }
    }

    [Serializable]
    public class DiceDivider : IDice
    {
        private IDice dice;
        private int divident;
        private RoundType round;



        public DiceDivider(IDice dice, int divident, RoundType round = RoundType.RoundNatural)
        {
            Contract.Requires(divident != 0);
            this.divident = divident;
            this.dice = dice;
            this.round = round;
        }

        public IDice Dice
        {
            get { return dice; }
        }
        public int Divident
        {
            get { return divident; }
        }


        public RoundType Round
        {
            get { return round; }
        }
        public int Roll()
        {
            var roled = (double)dice.Roll();
            return RoundMethod(roled);

        }

        private int RoundMethod(double roled)
        {
            switch (round)
            {
                case RoundType.RoundUp:
                    return (int)Math.Ceiling(roled / divident);
                case RoundType.RoundDown:
                    return (int)Math.Floor(roled / divident);
                case RoundType.RoundNatural:
                    return (int)Math.Round(roled / divident, MidpointRounding.AwayFromZero);
                default:
                    throw new NotImplementedException();
            }
        }

        public override string ToString()
        {
            return dice.ToString() + " / " + divident;
        }

        public enum RoundType
        {
            RoundUp,
            RoundDown,
            RoundNatural
        }


        public int MinValue
        {
            get { return RoundMethod(dice.MinValue); }
        }

        public int MaxValue
        {
            get { return RoundMethod(dice.MaxValue); }
        }

        public double DurchnitsValue
        {
            get { return RoundMethod(dice.DurchnitsValue); }
        }
    }

    [Serializable]
    public class DiceNumber : IDice
    {
        public DiceNumber(int number)
        {
            this.number = number;
        }
        public int Roll()
        {
            return number;
        }

        public int MinValue
        {
            get { return number; }
        }

        public int MaxValue
        {
            get { return number; }
        }

        public double DurchnitsValue
        {
            get { return number; }
        }
        public override string ToString()
        {
            return number.ToString();
        }

        public int number;
    }

    public class FunctionDice<T> : IDice
    {
        private Func<int, T> func;

        private IDice dice;

        private int minValue = int.MaxValue;
        private int maxValue = int.MinValue;

        public FunctionDice(IDice dice, Func<int, T> func)
        {

            this.dice = dice;

            if (func is Func<int, int>)
            {
                var intfunc = func as Func<int, int>;
                for (int i = dice.MinValue; i <= dice.MaxValue; i++)
                {
                    minValue = Math.Min(intfunc(i), minValue);
                    maxValue = Math.Max(intfunc(i), maxValue);
                }
            }
            this.func = func;
        }


        #region IDice Members

        public int Roll()
        {
            if (new int() is T)
                return (func as Func<int, int>)(dice.Roll());
            return dice.Roll();
        }

        public T RollT()
        {
            return func(dice.Roll());
        }

        public int MinValue
        {
            get
            {
                if (!(new int() is T))
                    throw new NotSupportedException();
                return minValue;
            }
        }

        public int MaxValue
        {
            get
            {
                if (!(new int() is T))
                    throw new NotSupportedException();
                return maxValue;
            }
        }

        public double DurchnitsValue
        {
            get
            {
                throw new NotSupportedException();
            }
        }

        #endregion
    }

    public interface IDice
    {
        int Roll();
        int MinValue { get; }
        int MaxValue { get; }
        double DurchnitsValue { get; }
    }
}
